agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v2 01/12] Add subprocess infrastructure 269+ messages / 2 participants [nested] [flat]
* [PATCH v2 01/12] Add subprocess infrastructure @ 2020-03-09 22:12 Mike Palmiotto <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Mike Palmiotto @ 2020-03-09 22:12 UTC (permalink / raw) This commit introduces a new infrastructure for postmaster subprocesses initialization, forking, exec'ing and main function control. The intent is to have all subprocess control information live in the process_types struct, defined in subprocess.h. As more processes are added to the struct, the fields will be expanded to differentiate actions. There are several advantages to such an infrastructure: 1) Centralized definition of subprocess information 2) Standardized function arguments/naming schemes 3) Code minimization and removal of duplicate code --- src/backend/bootstrap/bootstrap.c | 74 +++++++-------------------- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/bgwriter.c | 2 +- src/backend/postmaster/checkpointer.c | 2 +- src/backend/postmaster/startup.c | 3 +- src/backend/postmaster/subprocess.c | 62 ++++++++++++++++++++++ src/backend/postmaster/walwriter.c | 2 +- src/backend/replication/walreceiver.c | 2 +- src/include/bootstrap/bootstrap.h | 3 +- src/include/postmaster/bgwriter.h | 4 +- src/include/postmaster/startup.h | 5 +- src/include/postmaster/subprocess.h | 44 ++++++++++++++++ src/include/postmaster/walwriter.h | 2 +- src/include/replication/walreceiver.h | 2 +- 14 files changed, 140 insertions(+), 68 deletions(-) create mode 100644 src/backend/postmaster/subprocess.c create mode 100644 src/include/postmaster/subprocess.h diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 5480a024e0..9d41567467 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -35,6 +35,7 @@ #include "pgstat.h" #include "postmaster/bgwriter.h" #include "postmaster/startup.h" +#include "postmaster/subprocess.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" #include "storage/bufmgr.h" @@ -56,8 +57,6 @@ uint32 bootstrap_data_checksum_version = 0; /* No checksum */ #define ALLOC(t, c) \ ((t *) MemoryContextAllocZero(TopMemoryContext, (unsigned)(c) * sizeof(t))) -static void CheckerModeMain(void); -static void BootstrapModeMain(void); static void bootstrap_signals(void); static void ShutdownAuxiliaryProcess(int code, Datum arg); static Form_pg_attribute AllocateAttribute(void); @@ -334,6 +333,12 @@ AuxiliaryProcessMain(int argc, char *argv[]) default: MyBackendType = B_INVALID; } + + /* + * We've read the arguments and know what backend type we are. + */ + InitMySubprocess((SubprocessType)MyAuxProcType); + if (IsUnderPostmaster) init_ps_display(NULL); @@ -418,56 +423,10 @@ AuxiliaryProcessMain(int argc, char *argv[]) */ SetProcessingMode(NormalProcessing); - switch (MyAuxProcType) - { - case CheckerProcess: - /* don't set signals, they're useless here */ - CheckerModeMain(); - proc_exit(1); /* should never return */ - - case BootstrapProcess: - - /* - * There was a brief instant during which mode was Normal; this is - * okay. We need to be in bootstrap mode during BootStrapXLOG for - * the sake of multixact initialization. - */ - SetProcessingMode(BootstrapProcessing); - bootstrap_signals(); - BootStrapXLOG(); - BootstrapModeMain(); - proc_exit(1); /* should never return */ - - case StartupProcess: - /* don't set signals, startup process has its own agenda */ - StartupProcessMain(); - proc_exit(1); /* should never return */ - - case BgWriterProcess: - /* don't set signals, bgwriter has its own agenda */ - BackgroundWriterMain(); - proc_exit(1); /* should never return */ - - case CheckpointerProcess: - /* don't set signals, checkpointer has its own agenda */ - CheckpointerMain(); - proc_exit(1); /* should never return */ - - case WalWriterProcess: - /* don't set signals, walwriter has its own agenda */ - InitXLOGAccess(); - WalWriterMain(); - proc_exit(1); /* should never return */ - - case WalReceiverProcess: - /* don't set signals, walreceiver has its own agenda */ - WalReceiverMain(); - proc_exit(1); /* should never return */ + /* Now jump into the subprocess main function and never look back! */ + MySubprocess->entrypoint(argc, argv); - default: - elog(PANIC, "unrecognized process type: %d", (int) MyAuxProcType); - proc_exit(1); - } + proc_exit(1); /* should never return */ } /* @@ -476,8 +435,8 @@ AuxiliaryProcessMain(int argc, char *argv[]) * settings). Since, in fact, that was already done by BaseInit(), * we have nothing more to do here. */ -static void -CheckerModeMain(void) +void +CheckerModeMain(int argc, char *argv[]) { proc_exit(0); } @@ -489,11 +448,15 @@ CheckerModeMain(void) * The bootstrap backend doesn't speak SQL, but instead expects * commands in a special bootstrap language. */ -static void -BootstrapModeMain(void) +void +BootstrapModeMain(int argc, char *argv[]) { int i; + SetProcessingMode(BootstrapProcessing); + bootstrap_signals(); + BootStrapXLOG(); + Assert(!IsUnderPostmaster); Assert(IsBootstrapProcessingMode()); @@ -508,7 +471,6 @@ BootstrapModeMain(void) * Do backend-like initialization for bootstrap mode */ InitProcess(); - InitPostgres(NULL, InvalidOid, NULL, InvalidOid, NULL, false); /* Initialize stuff for bootstrap-file processing */ diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index bfdf6a833d..52b4c2142c 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -23,6 +23,7 @@ OBJS = \ pgstat.o \ postmaster.o \ startup.o \ + subprocess.o \ syslogger.o \ walwriter.o diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c index 069e27e427..b2170938f3 100644 --- a/src/backend/postmaster/bgwriter.c +++ b/src/backend/postmaster/bgwriter.c @@ -91,7 +91,7 @@ static XLogRecPtr last_snapshot_lsn = InvalidXLogRecPtr; * basic execution environment, but not enabled signals yet. */ void -BackgroundWriterMain(void) +BackgroundWriterMain(int argc, char *argv[]) { sigjmp_buf local_sigjmp_buf; MemoryContext bgwriter_context; diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index e354a78725..83f7be1b1e 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -180,7 +180,7 @@ static void ReqCheckpointHandler(SIGNAL_ARGS); * basic execution environment, but not enabled signals yet. */ void -CheckpointerMain(void) +CheckpointerMain(int argc, char *argv[]) { sigjmp_buf local_sigjmp_buf; MemoryContext checkpointer_context; diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index c2250d7d4e..e29649a6a1 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -127,13 +127,12 @@ HandleStartupProcInterrupts(void) ProcessProcSignalBarrier(); } - /* ---------------------------------- * Startup Process main entry point * ---------------------------------- */ void -StartupProcessMain(void) +StartupProcessMain(int argc, char *argv[]) { /* * Properly accept or ignore signals the postmaster might send us. diff --git a/src/backend/postmaster/subprocess.c b/src/backend/postmaster/subprocess.c new file mode 100644 index 0000000000..3e7a45bf10 --- /dev/null +++ b/src/backend/postmaster/subprocess.c @@ -0,0 +1,62 @@ +/*------------------------------------------------------------------------- + * + * subprocess.c + * + * Copyright (c) 2004-2020, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/syslogger.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" +#include "bootstrap/bootstrap.h" +#include "postmaster/bgwriter.h" +#include "postmaster/postmaster.h" +#include "postmaster/startup.h" +#include "postmaster/subprocess.h" +#include "postmaster/walwriter.h" +#include "replication/walreceiver.h" + +SubprocessType MySubprocessType; +PgSubprocess *MySubprocess; + +static PgSubprocess process_types[] = { + { + .desc = "checker", + .entrypoint = CheckerModeMain + }, + { + .desc = "bootstrap", + .entrypoint = BootstrapModeMain + }, + { + .desc = "startup", + .entrypoint = StartupProcessMain + }, + { + .desc = "background writer", + .entrypoint = BackgroundWriterMain + }, + { + .desc = "checkpointer", + .entrypoint = CheckpointerMain + }, + { + .desc = "wal writer", + .entrypoint = WalWriterMain + }, + { + .desc = "wal receiver", + .entrypoint = WalReceiverMain + } +}; + +void +InitMySubprocess(SubprocessType type) +{ + MySubprocessType = type; + MySubprocess = &process_types[type]; + MySubprocess->desc = gettext(MySubprocess->desc); +} diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c index 45a2757969..3a0cec3371 100644 --- a/src/backend/postmaster/walwriter.c +++ b/src/backend/postmaster/walwriter.c @@ -85,7 +85,7 @@ int WalWriterFlushAfter = 128; * basic execution environment, but not enabled signals yet. */ void -WalWriterMain(void) +WalWriterMain(int argc, char *argv[]) { sigjmp_buf local_sigjmp_buf; MemoryContext walwriter_context; diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index 25e0333c9e..cd39a8c982 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -165,7 +165,7 @@ ProcessWalRcvInterrupts(void) /* Main entry point for walreceiver process */ void -WalReceiverMain(void) +WalReceiverMain(int argc, char *argv[]) { char conninfo[MAXCONNINFO]; char *tmp_conninfo; diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h index b67fa9acc1..f85cfb5202 100644 --- a/src/include/bootstrap/bootstrap.h +++ b/src/include/bootstrap/bootstrap.h @@ -16,7 +16,6 @@ #include "nodes/execnodes.h" - /* * MAXATTR is the maximum number of attributes in a relation supported * at bootstrap time (i.e., the max possible in a system table). @@ -31,6 +30,8 @@ extern Relation boot_reldesc; extern Form_pg_attribute attrtypes[MAXATTR]; extern int numattr; +extern void CheckerModeMain(int argc, char *argv[]); +extern void BootstrapModeMain(int argc, char *argv[]); extern void AuxiliaryProcessMain(int argc, char *argv[]) pg_attribute_noreturn(); diff --git a/src/include/postmaster/bgwriter.h b/src/include/postmaster/bgwriter.h index 0a5708b32e..6fb1ee1d6d 100644 --- a/src/include/postmaster/bgwriter.h +++ b/src/include/postmaster/bgwriter.h @@ -27,8 +27,8 @@ extern int CheckPointTimeout; extern int CheckPointWarning; extern double CheckPointCompletionTarget; -extern void BackgroundWriterMain(void) pg_attribute_noreturn(); -extern void CheckpointerMain(void) pg_attribute_noreturn(); +extern void BackgroundWriterMain(int argc, char *argv[]) pg_attribute_noreturn(); +extern void CheckpointerMain(int argc, char *argv[]) pg_attribute_noreturn(); extern void RequestCheckpoint(int flags); extern void CheckpointWriteDelay(int flags, double progress); diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h index 9f59c1ffa3..6a8108dfe0 100644 --- a/src/include/postmaster/startup.h +++ b/src/include/postmaster/startup.h @@ -13,10 +13,13 @@ #define _STARTUP_H extern void HandleStartupProcInterrupts(void); -extern void StartupProcessMain(void) pg_attribute_noreturn(); extern void PreRestoreCommand(void); extern void PostRestoreCommand(void); extern bool IsPromoteTriggered(void); extern void ResetPromoteTriggered(void); +/* Startup subprocess functions */ +extern void StartupProcessMain(int argc, char *argv[]) pg_attribute_noreturn(); +extern bool StartupCleanup(int child_errno); + #endif /* _STARTUP_H */ diff --git a/src/include/postmaster/subprocess.h b/src/include/postmaster/subprocess.h new file mode 100644 index 0000000000..56e8edf2d8 --- /dev/null +++ b/src/include/postmaster/subprocess.h @@ -0,0 +1,44 @@ +/*------------------------------------------------------------------------- + * + * subprocess.h + * + * Copyright (c) 1996-2020, PostgreSQL Global Development Group + * + * src/include/postmaster/subprocess.h + * + *------------------------------------------------------------------------- + */ +#ifndef SUBPROCESS_H +#define SUBPROCESS_H + +#include "postmaster.h" + +typedef enum +{ + NoProcessType = -1, + CheckerType = 0, + BootstrapType, + StartupType, + BgWriterType, + CheckpointerType, + WalWriterType, + WalReceiverType, /* end of Auxiliary Process Forks */ + + NUMSUBPROCESSTYPES /* Must be last! */ +} SubprocessType; + +typedef void (*SubprocessEntryPoint) (int argc, char *argv[]); + +/* Current subprocess initializer */ +extern void InitMySubprocess(SubprocessType type); + +typedef struct PgSubprocess +{ + const char *desc; + SubprocessEntryPoint entrypoint; +} PgSubprocess; + +extern SubprocessType MySubprocessType; +extern PGDLLIMPORT PgSubprocess *MySubprocess; + +#endif /* SUBPROCESS_H */ diff --git a/src/include/postmaster/walwriter.h b/src/include/postmaster/walwriter.h index 011dc26a6e..596c437c54 100644 --- a/src/include/postmaster/walwriter.h +++ b/src/include/postmaster/walwriter.h @@ -16,6 +16,6 @@ extern int WalWriterDelay; extern int WalWriterFlushAfter; -extern void WalWriterMain(void) pg_attribute_noreturn(); +extern void WalWriterMain(int argc, char *argv[]) pg_attribute_noreturn(); #endif /* _WALWRITER_H */ diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h index e08afc6548..2025f19ab3 100644 --- a/src/include/replication/walreceiver.h +++ b/src/include/replication/walreceiver.h @@ -311,7 +311,7 @@ walrcv_clear_result(WalRcvExecResult *walres) } /* prototypes for functions in walreceiver.c */ -extern void WalReceiverMain(void) pg_attribute_noreturn(); +extern void WalReceiverMain(int argc, char *argv[]); extern void ProcessWalRcvInterrupts(void); /* prototypes for functions in walreceiverfuncs.c */ -- 2.17.0 --FcSpk3Icpd/Pbul4 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0002-Use-centralized-StartSubprocess-for-aux-procs.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-0-utf-8"; name="v1-0003-Add-alias-to-be-used-as-unset-state.patch" ^ permalink raw reply [nested|flat] 269+ messages in thread
* [PATCH v1 2/4] Introduce trenary reloptions @ 2025-08-29 14:12 Nikolay Shaplov <[email protected]> 0 siblings, 0 replies; 269+ messages in thread From: Nikolay Shaplov @ 2025-08-29 14:12 UTC (permalink / raw) Introduce trenary reloption as a replacement for current `vacuum_truncate` implementation. Remove `vacuum_truncate_set` additional flag and using `TRENARY_UNSET` value instead. --- src/backend/access/common/reloptions.c | 129 ++++++++++++++++++++----- src/backend/commands/vacuum.c | 4 +- src/include/access/reloptions.h | 26 ++--- src/include/c.h | 16 +++ src/include/utils/rel.h | 3 +- 5 files changed, 135 insertions(+), 43 deletions(-) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 0af3fea68fa..24662d277c8 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -40,9 +40,9 @@ * * To add an option: * - * (i) decide on a type (bool, integer, real, enum, string), name, default - * value, upper and lower bounds (if applicable); for strings, consider a - * validation routine. + * (i) decide on a type (bool, trenary, integer, real, enum, string), name, + * default value, upper and lower bounds (if applicable); for strings, + * consider a validation routine. * (ii) add a record below (or use add_<type>_reloption). * (iii) add it to the appropriate options struct (perhaps StdRdOptions) * (iv) add it to the appropriate handling routine (perhaps @@ -147,15 +147,6 @@ static relopt_bool boolRelOpts[] = }, false }, - { - { - "vacuum_truncate", - "Enables vacuum to truncate empty pages at the end of this table", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - true - }, { { "deduplicate_items", @@ -170,6 +161,21 @@ static relopt_bool boolRelOpts[] = {{NULL}} }; +static relopt_trenary trenaryRelOpts[] = +{ + { + { + "vacuum_truncate", + "Enables vacuum to truncate empty pages at the end of this table", + RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, + ShareUpdateExclusiveLock + }, + TRENARY_UNSET + }, + /* list terminator */ + {{NULL}} +}; + static relopt_int intRelOpts[] = { { @@ -600,6 +606,13 @@ initialize_reloptions(void) boolRelOpts[i].gen.lockmode)); j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + Assert(DoLockModesConflict(trenaryRelOpts[i].gen.lockmode, + trenaryRelOpts[i].gen.lockmode)); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, @@ -640,6 +653,14 @@ initialize_reloptions(void) j++; } + for (i = 0; trenaryRelOpts[i].gen.name; i++) + { + relOpts[j] = &trenaryRelOpts[i].gen; + relOpts[j]->type = RELOPT_TYPE_TRENARY; + relOpts[j]->namelen = strlen(relOpts[j]->name); + j++; + } + for (i = 0; intRelOpts[i].gen.name; i++) { relOpts[j] = &intRelOpts[i].gen; @@ -800,6 +821,9 @@ allocate_reloption(bits32 kinds, int type, const char *name, const char *desc, case RELOPT_TYPE_BOOL: size = sizeof(relopt_bool); break; + case RELOPT_TYPE_TRENARY: + size = sizeof(relopt_trenary); + break; case RELOPT_TYPE_INT: size = sizeof(relopt_int); break; @@ -883,6 +907,54 @@ add_local_bool_reloption(local_relopts *relopts, const char *name, add_local_reloption(relopts, (relopt_gen *) newoption, offset); } +/* + * init_trenary_reloption + * Allocate and initialize a new trenary reloption + */ +static relopt_trenary * +init_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption; + + newoption = (relopt_trenary *) allocate_reloption(kinds, + RELOPT_TYPE_TRENARY, name, desc, lockmode); + newoption->default_val = default_val; + + return newoption; +} + +/* + * add_trenary_reloption + * Add a new trenary reloption + */ +void +add_trenary_reloption(bits32 kinds, const char *name, const char *desc, + trenary default_val, LOCKMODE lockmode) +{ + relopt_trenary *newoption = init_trenary_reloption(kinds, name, desc, + default_val, lockmode); + + add_reloption((relopt_gen *) newoption); +} + +/* + * add_local_trenary_reloption + * Add a new trenary local reloption + * + * 'offset' is offset of trenary-typed field. + */ +void +add_local_trenary_reloption(local_relopts *relopts, const char *name, + const char *desc, trenary default_val, + int offset) +{ + relopt_trenary *newoption = init_trenary_reloption(RELOPT_KIND_LOCAL, + name, desc, + default_val, 0); + + add_local_reloption(relopts, (relopt_gen *) newoption, offset); +} /* * init_real_reloption @@ -1617,6 +1689,18 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len, option->gen->name, value))); } break; + case RELOPT_TYPE_TRENARY: + { + bool b; + parsed = parse_bool(value, &b); + option->values.trenary_val = b ? TRENARY_TRUE : TRENARY_FALSE; + if (validate && !parsed) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for trenary option \"%s\": %s", + option->gen->name, value))); + } + break; case RELOPT_TYPE_INT: { relopt_int *optint = (relopt_int *) option->gen; @@ -1780,17 +1864,6 @@ fillRelOptions(void *rdopts, Size basesize, char *itempos = ((char *) rdopts) + elems[j].offset; char *string_val; - /* - * If isset_offset is provided, store whether the reloption is - * set there. - */ - if (elems[j].isset_offset > 0) - { - char *setpos = ((char *) rdopts) + elems[j].isset_offset; - - *(bool *) setpos = options[i].isset; - } - switch (options[i].gen->type) { case RELOPT_TYPE_BOOL: @@ -1798,6 +1871,11 @@ fillRelOptions(void *rdopts, Size basesize, options[i].values.bool_val : ((relopt_bool *) options[i].gen)->default_val; break; + case RELOPT_TYPE_TRENARY: + *(trenary *) itempos = options[i].isset ? + options[i].values.trenary_val : + ((relopt_trenary *) options[i].gen)->default_val; + break; case RELOPT_TYPE_INT: *(int *) itempos = options[i].isset ? options[i].values.int_val : @@ -1912,8 +1990,8 @@ default_reloptions(Datum reloptions, bool validate, relopt_kind kind) offsetof(StdRdOptions, parallel_workers)}, {"vacuum_index_cleanup", RELOPT_TYPE_ENUM, offsetof(StdRdOptions, vacuum_index_cleanup)}, - {"vacuum_truncate", RELOPT_TYPE_BOOL, - offsetof(StdRdOptions, vacuum_truncate), offsetof(StdRdOptions, vacuum_truncate_set)}, + {"vacuum_truncate", RELOPT_TYPE_TRENARY, + offsetof(StdRdOptions, vacuum_truncate)}, {"vacuum_max_eager_freeze_failure_rate", RELOPT_TYPE_REAL, offsetof(StdRdOptions, vacuum_max_eager_freeze_failure_rate)} }; @@ -1993,7 +2071,6 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) elems[i].optname = opt->option->name; elems[i].opttype = opt->option->type; elems[i].offset = opt->offset; - elems[i].isset_offset = 0; /* not supported for local relopts yet */ i++; } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 733ef40ae7c..7b96c7f9a80 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2221,9 +2221,9 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, { StdRdOptions *opts = (StdRdOptions *) rel->rd_options; - if (opts && opts->vacuum_truncate_set) + if (opts && opts->vacuum_truncate != TRENARY_UNSET) { - if (opts->vacuum_truncate) + if (opts->vacuum_truncate == TRENARY_TRUE) params.truncate = VACOPTVALUE_ENABLED; else params.truncate = VACOPTVALUE_DISABLED; diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a604a4702c3..1a6717ed213 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -29,6 +29,7 @@ typedef enum relopt_type { RELOPT_TYPE_BOOL, + RELOPT_TYPE_TRENARY, /* on, off, unset */ RELOPT_TYPE_INT, RELOPT_TYPE_REAL, RELOPT_TYPE_ENUM, @@ -80,6 +81,7 @@ typedef struct relopt_value union { bool bool_val; + trenary trenary_val; int int_val; double real_val; int enum_val; @@ -94,6 +96,12 @@ typedef struct relopt_bool bool default_val; } relopt_bool; +typedef struct relopt_rernary +{ + relopt_gen gen; + int default_val; +} relopt_trenary; + typedef struct relopt_int { relopt_gen gen; @@ -152,19 +160,6 @@ typedef struct const char *optname; /* option's name */ relopt_type opttype; /* option's datatype */ int offset; /* offset of field in result struct */ - - /* - * isset_offset is an optional offset of a field in the result struct that - * stores whether the option is explicitly set for the relation or if it - * just picked up the default value. In most cases, this can be - * accomplished by giving the reloption a special out-of-range default - * value (e.g., some integer reloptions use -2), but this isn't always - * possible. For example, a Boolean reloption cannot be given an - * out-of-range default, so we need another way to discover the source of - * its value. This offset is only used if given a value greater than - * zero. - */ - int isset_offset; } relopt_parse_elt; /* Local reloption definition */ @@ -195,6 +190,8 @@ typedef struct local_relopts extern relopt_kind add_reloption_kind(void); extern void add_bool_reloption(bits32 kinds, const char *name, const char *desc, bool default_val, LOCKMODE lockmode); +extern void add_trenary_reloption(bits32 kinds, const char *name, + const char *desc, int default_val, LOCKMODE lockmode); extern void add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val, int min_val, int max_val, LOCKMODE lockmode); @@ -214,6 +211,9 @@ extern void register_reloptions_validator(local_relopts *relopts, extern void add_local_bool_reloption(local_relopts *relopts, const char *name, const char *desc, bool default_val, int offset); +extern void add_local_trenary_reloption(local_relopts *relopts, + const char *name, const char *desc, + trenary default_val, int offset); extern void add_local_int_reloption(local_relopts *relopts, const char *name, const char *desc, int default_val, int min_val, int max_val, int offset); diff --git a/src/include/c.h b/src/include/c.h index 39022f8a9dd..dec390c1e77 100644 --- a/src/include/c.h +++ b/src/include/c.h @@ -488,6 +488,22 @@ typedef void (*pg_funcptr_t) (void); #include <stdbool.h> +/* + * trenary + * Boolean value with an extrea "unset" option + * + * Trenary data type is used in relation options that can be "true", "false" or + * "unset". Since relation options are used deep inside the PostgreSQL code, + * this type is declared globally. +*/ + +typedef enum trenary +{ + TRENARY_FALSE = 0, + TRENARY_TRUE = 1, + TRENARY_UNSET = -1 +} trenary; + /* ---------------------------------------------------------------- * Section 3: standard system types diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index b552359915f..08f93bde007 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -346,8 +346,7 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog relation */ int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ - bool vacuum_truncate; /* enables vacuum to truncate a relation */ - bool vacuum_truncate_set; /* whether vacuum_truncate is set */ + trenary vacuum_truncate; /* enables vacuum to truncate a relation */ /* * Fraction of pages in a relation that vacuum can eagerly scan and fail -- 2.39.2 --nextPart6784952.tM3a2QDmDi Content-Disposition: attachment; filename="v1-0003-Add-alias-to-be-used-as-unset-state.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="unicode-2-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-03-09 22:12 [PATCH v2 01/12] Add subprocess infrastructure Mike Palmiotto <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/4] Introduce trenary reloptions Nikolay Shaplov <[email protected]> 2025-08-29 14:12 [PATCH v1 2/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